import v8 from "node:v8";
import { statSync, writeFileSync, createWriteStream } from "node:fs";
import Path from "node:path";
import Log4js from "log4js";

import { SystemStat, RestDto } from "qqlx-core";
import { isObject } from "qqlx-cdk";
import { getRandomBytes } from "./crypto";

const RANDOM_BYTES = getRandomBytes(6);

type LogContent = number | string | undefined;

/** 日志内容
 * @traceInfo 访客期望访问哪些资源
 * @log 文本日志
 * @detail 具体日志
 */
type LogOptions = { rest?: RestDto; log?: LogContent; detail?: object };

export class Logger {
  constructor() {}

  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░▄▄▄▄▄▄░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░▄█▀▀▀▀▀█▄░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▄█░░▄▄▄░░█▄░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▄▄█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▀▄▀▄▄▄▄▄▀▄▀░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

  /** 开始监听进程信号 */
  static doProcessWatch() {
    process.on("uncaughtException", (reason: Error) => {
      this.fatal({ log: `进程错误：uncaughtException`, detail: reason });
    });
    process.on("unhandledRejection", (reason: Error) => {
      this.fatal({ log: `进程异常：unhandledRejection`, detail: reason });
    });
  }

  // static async doProcessHeapSnapshot() {
  //   const shot_path = Path.join(process.cwd(), `heap-${Date.now()}.heapsnapshot`);
  //   this.info({ log: `开始生成内存快照：${shot_path}` });

  //   // 使用 v8 模块生成堆快照
  //   const snapshot = v8.getHeapSnapshot();
  //   const stream = createWriteStream(shot_path);
  //   snapshot.pipe(stream);

  //   stream.on("finish", () => {
  //     this.info({ log: `✅ 堆快照已保存: ${shot_path}` });
  //   });

  //   stream.on("error", (error) => {
  //     this.info({ log: "❌生成内存快照失败:", detail: error as any });
  //   });
  // }

  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░▄▄▄▄▄▄░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░▄█▀▀▀▀▀█▄░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▄█░░▄▄▄░░█▄░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▄▄█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▀▄▀▄▄▄▄▄▀▄▀░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

  static doSettleLog<T>(rest: RestDto, title: string, actions: PromiseSettledResult<T>[]) {
    actions.forEach((d: PromiseSettledResult<T>) => {
      this.debug({ rest, log: title, detail: { result: d.status, reason: (d as PromiseRejectedResult).reason } });
    });
  }

  /** 快速获取日志文件情况
   * @page log4js 不能将最新的日志插入第一行、而原始文件流也必须逐行读取，不容易分页
   * @size bytes
   */
  static getFileStat(): SystemStat {
    const logs = { value: 0 };
    logs.value += statSync(this.TRACE_PATH).size;
    logs.value += statSync(this.DEBUG_PATH).size;
    logs.value += statSync(this.INFO_PATH).size;
    logs.value += statSync(this.WARN_PATH).size;
    logs.value += statSync(this.ERROR_PATH).size;
    logs.value += statSync(this.FATAL_PATH).size;

    const { rss } = process.memoryUsage();
    return { logs: logs.value, memory: rss };
  }

  static logMemery(rest?: RestDto) {
    const $ins = Log4js.getLogger("info");
    const locates = this.locate(rest);
    if (locates.length > 0) $ins.info(locates.join("；"));

    // Title
    const { rss } = process.memoryUsage();
    const log = `当前内存=${(rss / 1024 / 1024).toFixed(3)}MB`;
    $ins.info(this.link(rest?.traceInfo, log).join("；"));
  }

  // ===================== Base =====================
  // ===================== Base =====================
  // ===================== Base =====================
  // ===================== Base =====================
  // ===================== Base =====================

  /** 大范围使用的地方，出现了错误 */
  static trace(options?: LogOptions) {
    const $ins = Log4js.getLogger("trace");
    const { rest, log, detail } = options || {};

    const locates = this.locate(rest);
    if (locates.length > 0) $ins.trace(locates.join("；"));

    // Title
    $ins.trace(this.link(rest?.traceInfo, log).join("；"));

    // Intro
    const { str_stringify, str_string } = this.getString(detail);
    if (str_stringify) $ins.trace(this.link(rest?.traceInfo, str_stringify).join("；"));
    if (str_string) $ins.trace(this.link(rest?.traceInfo, str_string).join("；"));
  }

  /** 调试容易发生的错误的时候*/
  static debug(options?: LogOptions) {
    const $ins = Log4js.getLogger("debug");
    const { rest, log, detail } = options || {};

    const locates = this.locate(rest);
    if (locates.length > 0) $ins.debug(locates.join("；"));

    // Title
    $ins.debug(this.link(rest?.traceInfo, log).join("；"));

    // Intro
    const { str_stringify, str_string } = this.getString(detail);
    if (str_stringify) $ins.debug(this.link(rest?.traceInfo, str_stringify).join("；"));
    if (str_string) $ins.debug(this.link(rest?.traceInfo, str_string).join("；"));
  }

  /** 需要持续收集的重要信息 */
  static info(options?: LogOptions) {
    const $ins = Log4js.getLogger("info");
    const { rest, log, detail } = options || {};

    const locates = this.locate(rest);
    if (locates.length > 0) $ins.info(locates.join("；"));

    // Title
    $ins.info(this.link(rest?.traceInfo, log).join("；"));

    // Intro
    const { str_stringify, str_string } = this.getString(detail);
    if (str_stringify) $ins.info(this.link(rest?.traceInfo, str_stringify).join("；"));
    if (str_string) $ins.info(this.link(rest?.traceInfo, str_string).join("；"));
  }

  /** 已知错误，系统将继续运行*/
  static warn(options?: LogOptions) {
    const $ins = Log4js.getLogger("warn");
    const { rest, log, detail } = options || {};

    const locates = this.locate(rest);
    if (locates.length > 0) $ins.warn(locates.join("；"));

    // Title
    $ins.warn(this.link(rest?.traceInfo, log).join("；"));

    // Intro
    const { str_stringify, str_string } = this.getString(detail);
    if (str_stringify) $ins.warn(this.link(rest?.traceInfo, str_stringify).join("；"));
    if (str_string) $ins.warn(this.link(rest?.traceInfo, str_string).join("；"));
  }

  /** 未知错误，系统将继续运行*/
  static error(options?: LogOptions) {
    const $ins = Log4js.getLogger("error");
    const { rest, log, detail } = options || {};

    const locates = this.locate(rest);
    if (locates.length > 0) $ins.error(locates.join("；"));

    // Title
    $ins.error(this.link(rest?.traceInfo, log).join("；"));

    // Intro
    const { str_stringify, str_string } = this.getString(detail);
    if (str_stringify) $ins.error(this.link(rest?.traceInfo, str_stringify).join("；"));
    if (str_string) $ins.error(this.link(rest?.traceInfo, str_string).join("；"));
  }

  /** 严重错误，系统即将退出*/
  static fatal(options?: LogOptions) {
    const $ins = Log4js.getLogger("fatal");
    const { rest, log, detail } = options || {};

    const locates = this.locate(rest);
    if (locates.length > 0) $ins.fatal(locates.join("；"));

    // Title
    $ins.fatal(this.link(rest?.traceInfo, log).join("；"));

    // Intro
    const { str_stringify, str_string } = this.getString(detail);
    if (str_stringify) $ins.fatal(this.link(rest?.traceInfo, str_stringify).join("；"));
    if (str_string) $ins.fatal(this.link(rest?.traceInfo, str_string).join("；"));
  }

  /** 具体日志 */
  static link(traceInfo?: RestDto["traceInfo"], content?: LogContent) {
    return [traceInfo?.traceId, content].filter(Boolean);
  }

  /** 希望访问哪个资源路径 / 谁在访问？*/
  static locate(rest?: RestDto) {
    const result = { wanner: [], who: [] } as { wanner: LogContent[]; who: LogContent[] };
    if (rest) {
      result.wanner = (
        rest.traceInfo?.path
          ? [
              //
              rest.traceInfo?.traceId,
              rest.traceInfo?.action,
              rest.traceInfo?.path,
              rest.traceInfo?.traceIp,
            ]
          : []
      ).filter(Boolean);

      result.who = rest?.visitorId ? [`visitorId=${rest.visitorId}`, `visitorType=${rest.visitorType}`] : [];
    }
    return result.wanner.concat(result.who);
  }

  static getString(obj?: any) {
    const result = { str_stringify: "", str_string: "" };
    if (isObject(obj)) {
      const _obj = obj as any;
      delete _obj.visitorId;
      delete _obj.visitorType;

      // 1
      const str_1 = JSON.stringify(_obj);
      if (str_1 !== "{}") result.str_stringify = `Stringify = ${str_1}`;

      // 2
      const str_2 = _obj.toString();
      if (str_2 !== "[object Object]") result.str_string = `toString = ${str_2}`;
    } else if (obj) {
      const msg = `${String(obj)}（not a object）`;
      result.str_string = msg;
    }
    return result;
  }

  // ===================== Config =====================
  // ===================== Config =====================
  // ===================== Config =====================
  // ===================== Config =====================
  // ===================== Config =====================

  private static TRACE_PATH = Path.join(process.cwd(), `temp-trace.${RANDOM_BYTES.hex}.log`);
  private static DEBUG_PATH = Path.join(process.cwd(), `temp-debug.${RANDOM_BYTES.hex}.log`);
  private static INFO_PATH = Path.join(process.cwd(), `temp-info.${RANDOM_BYTES.hex}.log`);
  private static WARN_PATH = Path.join(process.cwd(), `temp-warn.${RANDOM_BYTES.hex}.log`);
  private static ERROR_PATH = Path.join(process.cwd(), `temp-error.${RANDOM_BYTES.hex}.log`);
  private static FATAL_PATH = Path.join(process.cwd(), `temp-fatal.${RANDOM_BYTES.hex}.log`);

  static initial(basePath: string) {
    this.TRACE_PATH = this.TRACE_PATH.replace(RANDOM_BYTES.hex, basePath);
    this.DEBUG_PATH = this.DEBUG_PATH.replace(RANDOM_BYTES.hex, basePath);
    this.INFO_PATH = this.INFO_PATH.replace(RANDOM_BYTES.hex, basePath);
    this.WARN_PATH = this.WARN_PATH.replace(RANDOM_BYTES.hex, basePath);
    this.ERROR_PATH = this.ERROR_PATH.replace(RANDOM_BYTES.hex, basePath);
    this.FATAL_PATH = this.FATAL_PATH.replace(RANDOM_BYTES.hex, basePath);

    // console.log("TRACE_PATH", this.TRACE_PATH);
    // console.log("DEBUG_PATH", this.DEBUG_PATH);
    // console.log("INFO_PATH", this.INFO_PATH);
    // console.log("WARN_PATH", this.WARN_PATH);
    // console.log("ERROR_PATH", this.ERROR_PATH);
    // console.log("FATAL_PATH", this.FATAL_PATH);

    Log4js.configure({
      /** 定义日志是如何输出的（默认在当前进程目录） */
      appenders: {
        traceFile: { type: "file", filename: this.TRACE_PATH },
        debugFile: { type: "file", filename: this.DEBUG_PATH },
        infoFile: { type: "file", filename: this.INFO_PATH },
        warnFile: { type: "file", filename: this.WARN_PATH },
        errorFile: { type: "file", filename: this.ERROR_PATH },
        fatalFile: { type: "file", filename: this.FATAL_PATH },
      },

      /** 在日志记录中，注明其是哪一个分类，默认是 default
       * @level 默认会看到 trace 及更高级别的日志
       */
      categories: {
        // 5min 清理一次
        trace: { appenders: ["traceFile"], level: "trace" },
        debug: { appenders: ["debugFile"], level: "debug" },
        // 每天凌晨清理一次
        default: { appenders: ["infoFile"], level: "info" },
        info: { appenders: ["infoFile"], level: "info" },
        warn: { appenders: ["warnFile"], level: "warn" },
        error: { appenders: ["errorFile"], level: "error" },
        fatal: { appenders: ["fatalFile"], level: "fatal" },
      },
    });

    // 开始监听
    this.doProcessWatch();
  }

  static clear() {
    writeFileSync(this.TRACE_PATH, "");
    writeFileSync(this.DEBUG_PATH, "");
    writeFileSync(this.INFO_PATH, "");
    writeFileSync(this.WARN_PATH, "");
    writeFileSync(this.ERROR_PATH, "");
    writeFileSync(this.FATAL_PATH, "");
  }
}

/** 在一行中持续打印信息
 * @\r 光标回到行首
 * @\x1b[K 清除从光标位置到行尾的内容
 */
export function doBashPrint(str: string, isEnd?: boolean) {
  process.stdout.write("\r\x1b[K");
  process.stdout.write(str + (isEnd ? "\n" : "\r"));
}
